package consistenthash

import (
	"errors"
	"hash/crc32"
	"sort"
	"strconv"
)

const DefaultReplicas = 50

// The hash function is used to generate the hash value
type Hash func(data []byte) uint32

// Hash table
type Map struct {
	hash     Hash           // The hash function is used to generate the hash value
	replicas int            // Number of virtual nodes
	keys     []int          // Hash ring
	hashMap  map[int]string // Store virtual node and real node mapping relationships key->virtual node id value->real node id
}

func NewDefaultHashRing() (*Map, error) {
	return NewHashRing(DefaultReplicas, crc32.ChecksumIEEE)
}

func NewHashRing(replicas int, hash Hash) (*Map, error) {
	if replicas < 1 {
		return nil, errors.New("replicas cannot be less than 1")
	}
	return &Map{
		replicas: replicas,
		hash:     hash,
		hashMap:  make(map[int]string),
	}, nil
}

// add node
func (m *Map) Set(keys ...string) {
	for _, key := range keys {
		for i := 0; i < m.replicas; i++ {
			hash := int(m.hash([]byte(strconv.Itoa(i) + key)))
			m.keys = append(m.keys, hash)
			m.hashMap[hash] = key
		}
	}
	sort.Ints(m.keys)
}

// get real node
func (m *Map) Get(key string) string {
	if len(m.keys) == 0 {
		return ""
	}
	hash := int(m.hash([]byte(key)))

	idx := sort.Search(len(m.keys), func(i int) bool {
		return m.keys[i] >= hash
	})

	length := len(m.keys)
	return m.hashMap[m.keys[idx%length]]
}
